// C source file with the function definitions to handle J3DGRAPH files

// check_j3dgraph_fstart() function
// check the first 4 bytes of a j3dgraph file to see if it is valid
bool check_j3dgraph_fstart(char * fpath)
{
  // check params
  byte * ptr = NULL;
  bool rtn = false;
  if (fpath == NULL)
    goto end;
  
  // array for the bytes
  ptr = get_fbytes(fpath, 0, 4);
  if (ptr == NULL)
    goto end;
  
  // compare the array with J3DGRAPH_FSTART  
  for (byte i = 0; i < 2; i++)
    for (byte j = 0; j < 2; j++)
      rtn = rtn || comp_bytes(ptr, J3DGRAPH_FSTART[i][j], 4);
  
  // return the result, free memory used
  end:
  free_memory(ptr);
  return rtn;
}

// get_j3dgraph_fendian() function
// function to determine the endian mode of a j3dgraph file
ENDIAN_T get_j3dgraph_fendian(char * fpath)
{
  // check params
  ENDIAN_T endian = NO_ENDIAN;
  byte * ptr = NULL;
  if (fpath == NULL)
    goto end;
  
  // array for the bytes
  ptr = get_fbytes(fpath, 0, 4);
  if (ptr == NULL)
    goto end;
  
  // check file_start contents
  if (comp_bytes(ptr, J3DGRAPH_FSTART[0][0], 4) || comp_bytes(ptr, J3DGRAPH_FSTART[1][0], 4))
    endian = BIG;
  else if (comp_bytes(ptr, J3DGRAPH_FSTART[0][1], 4) || comp_bytes(ptr, J3DGRAPH_FSTART[1][1], 4))
    endian = LITTLE;
  
  // done
  end:
  free_memory(ptr);
  return endian;
}

// get_j3dgraph_str_weight() function
// function to return the string weight of a given
// string. String weight is defined for j3dgraph files.
// Value is truncated to an unsigned 16 bits integer.
// the string weight is the name I use (people use hash but
// I honestly have not learnt much about it to use the term with property)
// because I feel more familiar with it. The calculation process can be seen here
// https://humming-owl.neocities.org/smg-stuff/pages/tutorials/file-formats/btp#str-weight
umax get_j3dgraph_str_weight(void * str, umax mx_size)
{
  // check params
  if (str == NULL)
    return 0;
    
  // get the string length  
  umax str_length = get_str_byte_length_safe(str, mx_size);
  if (str_length == 0)
    return 0;
    
  // calculate the weight
  // the weight does not depend on the encoding but rather on the weight
  // of each byte of the string treated as a signed integer (char) 
  // so basically some bytes add magnitude to str_weight and others 
  // substract because they are read as negative. It is weird but yeah.
  umax str_weight = 0;
  for (umax i = 0; i < str_length; i++)
    str_weight += ((char *) str)[i] * powerof_uint(3, str_length - i - 1);
  
  // integer math process to get the truncated
  // part of the string weight (16 bits)  
  return str_weight & 0xFFFF;
}

// get_j3dgraph_padding_size() function
// function to get the size of a padding string in a j3dgraph file
umax get_j3dgraph_padding_size(umax padding_pos, bool is_32)
{
  umax size = 0;
  umax alignment = is_32 ? 32 : 4;
  while ((padding_pos + size) % alignment != 0)
    size++;
  return size;
}

// check_j3dgraph_str_tb() function
// function to check if a j3dgraph string table is valid
j3dgraph_str_tb_err_cd check_j3dgraph_str_tb(void * arr, umax mx_size, ENDIAN_T endian)
{
  // check params
  if (arr == NULL || mx_size < J3DGRAPH_MIN_STR_TB_LENGTH || endian == NO_ENDIAN)
    return J3DGRAPH_STR_TB_ERR_INV_ARGS;
  
  // "valid" error to return
  j3dgraph_str_tb_err_cd soft_err = J3DGRAPH_STR_TB_ERR_NO_ERR;
  
  // check if there is actually a string table in arr
  byte * ptr = arr;
  umax tmp = 0;
  // get the element count
  umax elem_cnt = get_std_uint(16, ptr, endian);
  // check unknown 1
  if (get_std_uint(16, ptr + 2, endian) != J3DGRAPH_STR_TB_UNKNOWN_1)
    soft_err = get_min_uint(soft_err, J3DGRAPH_STR_TB_ERR_UNKNOWN_1);
  ptr += 4;
  mx_size -= 4;
  
  // check the string table information list
  if (4 * elem_cnt >= mx_size - 1)
    return J3DGRAPH_STR_TB_ERR_INV_ELEM_CNT;
  for (umax i = 0, tmp2 = 0; i < elem_cnt; i++, ptr += 4, mx_size -= 4)
  {
    // string weight and string offset
    tmp = get_std_uint(16, ptr, endian);
    tmp2 = get_std_uint(16, ptr + 2, endian);
    if (tmp2 >= ptr - (byte *) arr + mx_size - 1) 
      return J3DGRAPH_STR_TB_ERR_INV_STR_OFFSET;
    // out of bounds string (the extreme case is a 1 char string at the end of the table)
    if (tmp != get_j3dgraph_str_weight((byte *) arr + tmp2, mx_size))
      soft_err = get_min_uint(soft_err, J3DGRAPH_STR_TB_ERR_INV_STR_WEIGHT);
    // I wont check if the strings are side by side
  }
  
  // change the CHAR_ARR encoding state
  char_enc old_enc = CHAR_ARR_ENC_STATE;
  set_ch_arr_enc_state(CHAR_ENC_CP932);
  
  // check the elem_cnt strings in here
  // if one is invalid then the table is invalid
  for (umax i = 0, tmp = 0; i < elem_cnt; i++) {
    tmp = get_str_byte_length_safe(ptr, mx_size);
    if (tmp == 0)
      return J3DGRAPH_STR_TB_ERR_INV_STR;
    ptr += tmp + chk_ch_int(0);
    mx_size -= tmp + chk_ch_int(0);
  }
  // reset CHAR_ARR encoding state
  set_ch_arr_enc_state(old_enc);
  
  // done!
  return soft_err;
}

// get_j3dgraph_str_tb_size() function
// function to get the byte size of a j3dgraph string table
umax get_j3dgraph_str_tb_size(void * arr, umax mx_size, ENDIAN_T endian)
{
  // check params
  if (check_j3dgraph_str_tb(arr, mx_size, endian) < J3DGRAPH_STR_TB_ERR_NO_ERR)
    return 0;
  
  // else compute the size
  byte * ptr = arr;
  umax tmp = 0, str_tb_size = 0;
  // get element count
  tmp = get_std_uint(16, ptr, endian);
  
  // element count, unknown 1 and string inf list
  ptr += 4 + (4 * tmp);
  str_tb_size += 4 + (4 * tmp);
  // [element count] strings
  char_enc old_enc = CHAR_ARR_ENC_STATE;
  set_ch_arr_enc_state(CHAR_ENC_CP932);
  for (umax i = 0, tmp2 = 0; i < tmp; i++) {
    tmp2 = get_str_byte_length(ptr);
    ptr += tmp2 + chk_ch_int(0);
    str_tb_size += tmp2 + chk_ch_int(0);
  }
  set_ch_arr_enc_state(old_enc);
  
  // done!
  return str_tb_size;
}

// print_j3dgraph_str_tb() function
// function to print the contents of a j3dgraph string table
j3dgraph_str_tb * get_j3dgraph_str_tb(void * src, umax src_size, ENDIAN_T endian)
{
  // check params
  j3dgraph_str_tb * str_tb = NULL;
  j3dgraph_str_tb_err_cd err_cd = 0;
  if (check_j3dgraph_str_tb(src, src_size, endian) < J3DGRAPH_STR_TB_ERR_NO_ERR)
    goto err;
  
  // allocate space for the structure
  str_tb = allocate_memory(1, sizeof(j3dgraph_str_tb));
  if (str_tb == NULL)
    goto err;
  
  // fill the structure
  byte * ptr = src;
  str_tb -> ENDIAN = endian;
  str_tb -> ERR_CD = err_cd;
  str_tb -> STR_CNT = ptr; ptr += 2;
  str_tb -> UNKNOWN_1 = ptr; ptr += 2;
  str_tb -> STR_INF_LIST = ptr;
  ptr += 4 * get_std_uint(16, str_tb -> STR_CNT, endian);
  str_tb -> STR_LIST = ptr;
  
  // done!
  return str_tb;
  // failure
  err:
  free_memory(str_tb);
  return NULL;
}

// close_j3dgraph_str_tb() function
// function to free the memory used by a j3dgraph_str_tb struct
void close_j3dgraph_str_tb(j3dgraph_str_tb * str_tb)
{
  // check params
  if (str_tb == NULL)
    return;
  // free memory
  free_memory(str_tb);
  return;
}

// print_j3dgraph_str_tb() function
// function to print the contents of a j3dgraph string table
void print_j3dgraph_str_tb(j3dgraph_str_tb * str_tb)
{
  // check params
  if (str_tb == NULL)
    return;
  
  // print the structure information
  umax str_cnt = get_std_uint(16, str_tb -> STR_CNT, str_tb -> ENDIAN);
  printf("Endian: %s, ", str_tb -> ENDIAN == BIG ? "BIG" : "LITTLE");
  printf("ERROR CODE: %d\n", str_tb -> ERR_CD);
  printf("String count: %llu, ", str_cnt);
  printf("Unknown 1: %04llX\n", get_std_uint(16, str_tb -> UNKNOWN_1, str_tb -> ENDIAN));
  
  // string information list
  byte * ptr = str_tb -> STR_INF_LIST;
  printf("## String inf list begin:\n");
  for (umax i = 0; i < str_cnt; i++, ptr += 4)
    printf("str[%llu] weight: 0x%04llX, offset: 0x%04llX\n", i, get_std_uint(16, ptr, str_tb -> ENDIAN),
                                                            get_std_uint(16, ptr + 2, str_tb -> ENDIAN));
  
  // string list
  // set CHAR_ARR encoding state to CP932 (for now I believe this is the encoding of strings)
  ptr = str_tb -> STR_LIST;
  printf("## String list begin:\n");
  char_enc old_enc = CHAR_ARR_ENC_STATE;
  set_ch_arr_enc_state(CHAR_ENC_CP932);
  for (umax i = 0, tmp = 0; i < str_cnt; i++) {
    printf("str[%llu] chars: ", i);
    tmp = get_str_byte_length(ptr);
    print_chenc_arr_as_unicd(ptr, tmp);
    ptr += tmp + chk_ch_int(0);
  }
  // reset CHAR_ARR state
  set_ch_arr_enc_state(old_enc);
  
  // done!
  printf("## String table end\n");
  return;
}

// check_j3dgraph_inc() function
// check a if j3dgraph file has a valid superficial structure
j3dgraph_err_cd check_j3dgraph_inc(char * fpath)
{
  // check params
  if (fpath == NULL)
    return J3DGRAPH_ERR_NULL_PATH;
  
  // first check the file start
  if (check_j3dgraph_fstart(fpath) == 0)
    return J3DGRAPH_ERR_FILE_START;
  
  // load file and get its size
  byte * fbytes = load_file_mem(fpath);
  if (fbytes == NULL)
    return J3DGRAPH_ERR_LOAD_MEM;
  umax fsize = get_file_size(fpath);
  
  // first file size check
  if (fsize <= 32 || (fsize % 32) != 0)
    return J3DGRAPH_ERR_FILE_SIZE;
  
  // get endianness
  ENDIAN_T fendian = get_j3dgraph_fendian(fpath);
  if (fendian == NO_ENDIAN)
    return J3DGRAPH_ERR_ENDIAN;
  
  // file size check
  umax read_fsize = get_std_uint(32, fbytes + 8, fendian);
  if (read_fsize != fsize || (read_fsize % 32) != 0)
  // ^ or some check with a minimum file size idk yet how to get
    return J3DGRAPH_ERR_FILE_SIZE;
  // get section count
  umax sec_count = get_std_uint(32, fbytes + 12, fendian);
  umax read_sec_count = 0;
  
  // check each section header
  umax sec_size_sum = 32;
  umax cur_sec_size = 0;
  for (umax i = 0; i < sec_count; i++)
  {
    // get section size (skip section start)
    cur_sec_size = get_std_uint(32, fbytes + sec_size_sum + 4, fendian);    
    // check current section size
    if (cur_sec_size % 32 != 0 || cur_sec_size > fsize)
      return J3DGRAPH_ERR_SEC_SIZE;    
    // go to the next section
    sec_size_sum += cur_sec_size;
    read_sec_count++;
  }
  
  // check section count
  if (read_sec_count != sec_count) 
    return J3DGRAPH_ERR_SEC_CNT;
    
  // check the section sizes sum
  if (sec_size_sum != fsize)
    return J3DGRAPH_ERR_FILE_SIZE;
  
  // checks done
  free_memory(fbytes);
  return J3DGRAPH_ERR_NO_ERR;
}


// open_j3dgraph() function
// function to partially fill a j3dgraph structure
// with the information of a j3dgraph valid file
j3dgraph * open_j3dgraph_inc(char * fpath)
{
  // check params
  j3dgraph_err_cd err_cd = check_j3dgraph_inc(fpath);
  if (err_cd > J3DGRAPH_ERR_LN)
    goto err;
  
  // pointer to return
  j3dgraph * fptr = allocate_memory(sizeof(j3dgraph), sizeof(byte));
  if (fptr == NULL)
    goto err;
  
  // fill the structure basics
  fptr -> PATH = fpath;
  fptr -> DATA = load_file_mem(fpath);
  fptr -> FSIZE = get_file_size(fpath);
  fptr -> ENDIAN = get_j3dgraph_fendian(fpath);
  fptr -> ERR_CD = err_cd;
  fptr -> HEADER.START = (fptr -> DATA);
  fptr -> HEADER.ID = (fptr -> DATA) + 4;
  fptr -> HEADER.SIZE = (fptr -> DATA) + 8; 
  fptr -> HEADER.SEC_CNT = (fptr -> DATA) + 12;
  fptr -> HEADER.UNKNOWN_1 = (fptr -> DATA) + 16;
  
  // array of pointers to structures
  fptr -> SEC_CNT = get_std_uint(32, fptr -> HEADER.SEC_CNT, fptr -> ENDIAN);
  fptr -> SEC = allocate_memory(fptr -> SEC_CNT, sizeof(void *));
  if ((fptr -> SEC) == NULL) {
    free_memory(fptr -> DATA);
    free_memory(fptr);
    goto err;
  }
  
  // fill each j3dgraph_gen_sec in fptr -> SEC
  umax fpos = 32;
  for (umax i = 0; i < (fptr -> SEC_CNT); i++)
  {    
    (fptr -> SEC)[i] = allocate_memory(1, sizeof(j3dgraph_gen_sec));
    if ((fptr -> SEC) == NULL) {
      // free the previously allocated memory
      for (umax j = 0; j < i; j++)
        free_memory((fptr -> SEC)[j]);
      free_memory(fptr -> SEC);
      free_memory(fptr -> DATA);
      free_memory(fptr);
      goto err;
    }
    ((j3dgraph_gen_sec *) (fptr -> SEC)[i]) -> START = fptr -> DATA + fpos;
    ((j3dgraph_gen_sec *) (fptr -> SEC)[i]) -> SIZE = fptr -> DATA + fpos + 4;
    fpos += get_std_uint(32, ((j3dgraph_gen_sec *) (fptr -> SEC)[i]) -> SIZE, fptr -> ENDIAN);
  }
  
  // all good
  return fptr;
  // failure
  err:
  return NULL;
}

// close_j3dgraph() function
// function to release the memory used by an incomplete j3dgraph structure
void close_j3dgraph_inc(j3dgraph * file)
{
  // check params
  if (file == NULL)
    return;
  
  // free the sections data, SEC array, DATA and the structure itself
  // free each (file -> SEC)[i]
  for (umax i = 0; i < (file -> SEC_CNT); i++)
    free_memory((file -> SEC)[i]);
  // free the rest of the structure
  free_memory(file -> SEC);
  free_memory(file -> DATA);
  free_memory(file);
  // done
  return;
}

// print_j3dgraph_start() function
// function to print the starting info of an incomplete j3dgraph struct
void print_j3dgraph_start(j3dgraph * file)
{
  // check params
  if (file == NULL)
    return;
  
  // file info and error code
  printf("File: %s\n", file -> PATH);
  printf("File size (bytes): %llu\n", file -> FSIZE);
  printf("File endian: %s\n", file -> ENDIAN == BIG ? "BIG" : "LITTLE");
  printf("FILE ERROR CODE: %u\n\n", file -> ERR_CD);
  
  // file header
  printf("File start: ");
  print_ascii_arr(file -> HEADER.START, 4);
  printf("File size (bytes): %llu\n", get_std_uint(32, file -> HEADER.SIZE, file -> ENDIAN));
  printf("Section count: %llu\n", get_std_uint(32, file -> HEADER.SEC_CNT, file -> ENDIAN));
  printf("Unknown 1: ");
  print_array(file -> HEADER.UNKNOWN_1, 16, 1, print_byte_hex);
  
  // done
  return;
}

// print_j3dgraph_inc() function
// function to print the contents of an incomplete j3dgraph structure
void print_j3dgraph_inc(j3dgraph * file)
{    
  // check ptr
  if (file == NULL)
    return;
  
  // starting visual queue
  printf("\n### J3DGraph incomplete struct info start ###\n\n");  
  // print the start of the j3dgraph struct
  print_j3dgraph_start(file);
  
  // section headers
  for (umax i = 0; i < (file -> SEC_CNT); i++) {
    printf("\nSection start: ");
    print_ascii_arr(((j3dgraph_gen_sec *) (file -> SEC)[i]) -> START, 4);
    printf("Section size (bytes): %llu\n",
           get_std_uint(32, ((j3dgraph_gen_sec *) (file -> SEC)[i]) -> SIZE, file -> ENDIAN));
  }  
  
  // end of structure
  printf("\n### J3DGraph incomplete struct info end ###\n");
  // done
  return;
}
